package eerbaar.internal.area;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import eerbaar.area.IArea;
import eerbaar.area.ISquare;
import eerbaar.area.SquareIterator;
import eerbaar.collision.ICollisionCallback;
import eerbaar.collision.ICollisionService;
import eerbaar.collision.IPetEventService;
import eerbaar.elements.IElement;
import eerbaar.elements.IElementContainer;
import eerbaar.elements.IElementListener;
import eerbaar.elements.extension.IEnemyElement;
import eerbaar.message.lib.EnemyKilledMsg;
import eerbaar.position.IPosition;
import eerbaar.position.WrongPositionException;
import eerbaar.shared.message.MsgHelper;
import eerbaar.shared.service.ServiceFactory;

public class Area implements IArea, IElementContainer {

    private final List<IElement> elements = new ArrayList<IElement>();
    private final List<Row> rows = new ArrayList<Row>();
    private final int height;
    private final int width;

    private final IElementListener elementListener = new IElementListener() {

        @Override
        public void afterMove(IPosition oldPos, IPosition newPos) {

            final List<IElement> remove = new ArrayList<IElement>();

            ICollisionCallback callback = new ICollisionCallback() {

                @Override
                public void removeElement(IElement element) {

                    Area.this.removeElement(element);
                    // if (!remove.contains(element)) {
                    // remove.add(element);
                    // }
                }
            };

            {
                ICollisionService service = ServiceFactory
                        .getService(ICollisionService.class);

                SquareIterator i = new SquareIterator(newPos, 2, false);
                IPosition cursor = null;
                while ((cursor = i.next()) != null) {

                    ISquare s1 = getSquare(cursor);
                    IElement[] a1 = s1.getElements();
                    ISquare s2 = getSquare(newPos);
                    IElement[] a2 = s2.getElements();

                    List<IElement> l = new ArrayList<IElement>();
                    Collections.addAll(l, a1);
                    Collections.addAll(l, a2);
                    if (l.size() > 1) {
                        IElement e1 = l.get(0);
                        IElement e2 = l.get(1);
                        service.processProximity(getPet(), e1, e2, callback);
                    }
                }
            }
            {
                ICollisionService service = ServiceFactory
                        .getService(ICollisionService.class);

                ISquare square = getSquare(newPos);

                IElement[] elements = square.getElements();
                if (elements.length > 1) {
                    IElement e1 = elements[0];
                    IElement e2 = elements[1];
                    service.process(e1, e2, callback);
                }
            }

            {
                IPetEventService service = ServiceFactory
                        .getService(IPetEventService.class);
                IElement[] elements = Area.this.getElements();
                service.process(elements, oldPos, newPos);
            }

            for (IElement e : remove) {
                Area.this.removeElement(e);

                if (e.getExtension(IEnemyElement.class) != null) {
                    EnemyKilledMsg msg = new EnemyKilledMsg();
                    MsgHelper.sendMsg(EnemyKilledMsg.class, msg);
                }
            }
        }

        @Override
        public void beforePositionChange(IPosition pos)
                throws WrongPositionException {

            if (!isIn(pos)) {
                throw new WrongPositionException();
            }
        }
    };

    public Area(int width, int height) {
        this.width = width;
        this.height = height;
        create(width, height);
    }

    @Override
    public void addElement(IElement element) {
        this.elements.add(element);
        element.addListener(this.elementListener);
    }

    private void create(int w, int h) {
        for (int y = 0; y < h; y++) {
            this.rows.add(new Row(this, w, y));
        }
    }

    @Override
    public IElement[] getElements() {
        return this.elements.toArray(new IElement[] {});
    }

    @Override
    public IElement[] getElements(IPosition position) {
        List<IElement> result = new ArrayList<IElement>();
        for (IElement e : this.elements) {
            if (e.getPosition().equals(position)) {
                result.add(e);
            }
        }
        return result.toArray(new IElement[] {});
    }

    @Override
    public int getHeight() {
        return this.height;
    }

    private IElement getPet() {

        for (IElement e : this.elements) {
            if (e.getName().equals("pet")) {
                return e;
            }
        }
        return null;
    }

    public ISquare getSquare(int x, int y) {

        if (y >= this.rows.size()) {
            throw new IllegalArgumentException("y is too big");
        }

        Row row = this.rows.get(y);
        if (x >= row.getSize()) {
            throw new IllegalArgumentException("x is too big");
        }

        return this.rows.get(y).getSquare(x);
    }

    @Override
    public ISquare getSquare(IPosition position) {

        int x = position.getX();
        int y = position.getY();

        return getSquare(x, y);
    }

    @Override
    public int getWidth() {
        return this.width;
    }

    public boolean isIn(IPosition position) {

        boolean result = true;

        if ((position.getX() >= this.width) || (position.getX() < 0)) {
            result = false;
        }
        if ((position.getY() >= this.height) || (position.getY() < 0)) {
            result = false;
        }

        return result;
    }

    @Override
    public void removeElement(IElement element) {
        this.elements.remove(element);
        element.removeListener(this.elementListener);
    }

    @Override
    public String toString() {
        String s = "area(";
        s += "height=" + this.height;
        s += ",";
        s += "width=" + this.width;
        s += ")";
        return s;
    }

}
